package cn.hbads.support.http.client;

import cn.hbads.support.error.CommonErrorType;
import cn.hbads.support.error.ErrorType;
import cn.hbads.support.http.client.model.HttpClientModel;
import cn.hbads.support.http.client.model.HttpRequestModel;
import cn.hbads.support.http.client.model.HttpResponseModel;
import cn.hbads.support.json.JsonUtil;
import cn.hbads.support.log.LogUtil;
import cn.hbads.support.structure.result.ComputeResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProxySelector;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * <b>Http客户端工具类</b>
 * <br/>对java.net.HttpClient的封装，旨在提供类似ORM的http-client服务
 *
 * @author yvvb
 * @date 2025/5/4
 */
public final class HttpClientUtil {
    public static final String LOCALHOST = "localhost";
    public static final int SUCCESS_HTTP_STATUS = 200;
    public static final GenericConversionService GENERIC_CONVERSION_SERVICE = new GenericConversionService();
    public static final HttpClient DEFAULT_HTTP_CLIENT;

    static {
        DEFAULT_HTTP_CLIENT = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_1_1)
                .followRedirects(HttpClient.Redirect.NORMAL)
                .connectTimeout(Duration.ofSeconds(20))
                .build();
    }

    private HttpClientUtil() {
    }

    public static <T> ComputeResult<HttpResponseModel<T>, ErrorType> sendRequest(HttpRequestModel requestModel, Class<T> expectedResponseBodyClass) {
        return sendRequest(null, requestModel, expectedResponseBodyClass);
    }

    public static <T> ComputeResult<HttpResponseModel<T>, ErrorType> sendRequest(HttpClientModel clientModel, HttpRequestModel requestModel, Class<T> expectedResponseBodyClass) {
        try {
            if (checkIfRequestModelValid(requestModel)) {
                return ComputeResult.fail(CommonErrorType.HTTP_REQUEST_MODEL_INVALID);
            }
            try (HttpClient httpClient = buildClientModel(clientModel)) {
                ComputeResult<HttpRequest, ErrorType> buildRequestResult = buildHttpRequest(clientModel, requestModel);
                if (!buildRequestResult.isSuccess()) {
                    return buildRequestResult.mapErr(t -> null);
                }
                HttpRequest httpRequest = buildRequestResult.get();
                HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
                return parseHttpResponse(httpResponse, expectedResponseBodyClass);
            }
        } catch (IOException e) {
            return ComputeResult.fail(CommonErrorType.HTTP_SEND_AND_PARSE_ERROR, e);
        } catch (InterruptedException e) {
            LogUtil.warn("Io operation had been interrupted");
            Thread.currentThread().interrupt();
            return ComputeResult.fail(CommonErrorType.HTTP_SEND_AND_PARSE_ERROR, e);
        }
    }

    private static boolean checkIfRequestModelValid(HttpRequestModel requestModel) {
        return Objects.nonNull(requestModel) && Objects.nonNull(requestModel.getRequestUri());
    }

    private static ComputeResult<HttpRequest, ErrorType> buildHttpRequest(HttpClientModel clientModel, HttpRequestModel requestModel) {
        // URI
        HttpRequest.Builder httpRequestBuilder = HttpRequest.newBuilder(requestModel.buildRequestUri());
        // Header
        String[] headers = requestModel.buildRequestHeaderArr();
        if (headers.length != 0) {
            httpRequestBuilder.headers(headers);
        }
        // TIME-OUT
        httpRequestBuilder.timeout(Objects.isNull(clientModel.timeout()) ? HttpClientModel.DEFAULT_TIME_OUT : clientModel.timeout());
        // BODY
        ComputeResult<HttpRequest.BodyPublisher, ErrorType> bodyPublisherResult = requestModel.buildRequestBodyPublisher();
        if (!bodyPublisherResult.isSuccess()) {
            return bodyPublisherResult.mapErr(t -> null);
        }
        HttpRequest.BodyPublisher bodyPublisher = bodyPublisherResult.get();
        httpRequestBuilder.method(requestModel.getHttpMethod().name(), bodyPublisher);
        return ComputeResult.success(httpRequestBuilder.build());
    }

    private static HttpClient buildClientModel(HttpClientModel clientModel) {
        if (Objects.isNull(clientModel)) {
            return DEFAULT_HTTP_CLIENT;
        }
        final HttpClient.Builder clientBuilder = HttpClient.newBuilder();
        // 版本
        if (Objects.nonNull(clientModel.version())) {
            clientBuilder.version(clientModel.version());
        } else {
            clientBuilder.version(HttpClient.Version.HTTP_1_1);
        }
        // 代理
        final String proxyHost = clientModel.proxyHost();
        final int proxyPort = clientModel.proxyPort();
        if (StringUtils.isNotBlank(proxyHost) && !LOCALHOST.equals(proxyHost) && proxyPort > 0) {
            clientBuilder.proxy(ProxySelector.of(new InetSocketAddress(proxyHost, proxyPort)));
        }
        // 超时
        if (Objects.nonNull(clientModel.timeout())) {
            clientBuilder.connectTimeout(clientModel.timeout());
        } else {
            clientBuilder.connectTimeout(Duration.ofSeconds(20));
        }

        // 重定向策略
        if (Objects.nonNull(clientModel.redirect())) {
            clientBuilder.followRedirects(clientModel.redirect());
        } else {
            // 默认允许重定向，除非是从Https到Http的重定向
            clientBuilder.followRedirects(HttpClient.Redirect.NORMAL);
        }

        return clientBuilder.build();
    }

    @SuppressWarnings("unchecked")
    private static <T> ComputeResult<HttpResponseModel<T>, ErrorType> parseHttpResponse(HttpResponse<String> httpResponse, Class<T> expectedResponseBodyClass) {
        int responseStatus = httpResponse.statusCode();
        String bodyStr = httpResponse.body();
        HttpResponseModel<T> responseModel = HttpResponseModel.<T>builder()
                .statusCode(responseStatus)
                .version(httpResponse.version())
                .originResponseBodyStr(bodyStr)
                .build();
        if (responseStatus != SUCCESS_HTTP_STATUS) {
            // 失败情况
            responseModel.setErrorResponseMap(JsonUtil.parseJsonToMap(bodyStr, String.class, String.class).toOptional());
        } else {
            // 成功的请求 状态码200
            // 响应体空
            if (StringUtils.isBlank(bodyStr)) {
                return ComputeResult.success(responseModel);
            }
            if (Objects.isNull(expectedResponseBodyClass)) {
                return ComputeResult.fail(CommonErrorType.EXPECT_RESPONSE_TYPE_NULL);
            }
            if (CharSequence.class.isAssignableFrom(expectedResponseBodyClass)) {
                responseModel.setResponseBody(Optional.of(GENERIC_CONVERSION_SERVICE.convert(bodyStr, expectedResponseBodyClass)));
            } else if (ByteArrayInputStream.class.isAssignableFrom(expectedResponseBodyClass)) {
                try {
                    responseModel.setResponseBody(Optional.of((T) new ByteArrayInputStream(bodyStr.getBytes())));
                } catch (NullPointerException | ClassCastException e) {
                    responseModel.setResponseBody(Optional.empty());
                }
            } else if (isJsonResponseMediaType(httpResponse) || isTextResponseMediaType(httpResponse)) {
                responseModel.setResponseBody(JsonUtil.useGeneric().parseJson(bodyStr, expectedResponseBodyClass).toOptional());
            } else {
                tryToCastToTargetType(expectedResponseBodyClass, responseModel, bodyStr);
            }
        }
        responseModel.setResponseHeaders(httpResponse.headers().map());
        return ComputeResult.success(responseModel);
    }

    private static boolean isTextResponseMediaType(HttpResponse<String> httpResponse) {
        List<String> contentTypeList = httpResponse.headers().allValues(HttpHeaders.CONTENT_TYPE);
        return contentTypeList.contains(MediaType.TEXT_HTML_VALUE)
                || contentTypeList.contains(MediaType.TEXT_PLAIN_VALUE)
                || contentTypeList.contains(MediaType.TEXT_XML_VALUE);
    }

    private static boolean isJsonResponseMediaType(HttpResponse<String> httpResponse) {
        List<String> contentTypeList = httpResponse.headers().allValues(HttpHeaders.CONTENT_TYPE);
        return contentTypeList.contains(MediaType.APPLICATION_JSON_VALUE);
    }

    private static <T> void tryToCastToTargetType(Class<T> expectedResponseBodyClass, HttpResponseModel<T> responseModel, String bodyStr) {
        try {
            responseModel.setResponseBody(Optional.of(GENERIC_CONVERSION_SERVICE.convert(bodyStr, expectedResponseBodyClass)));
        } catch (Exception e) {
            responseModel.setResponseBody(Optional.empty());
        }
    }
}
